﻿#region License
/*

	Copyright (C) 2008 Oliver Charles, Daniel Hollander

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
	
*/
#endregion

using System.Collections.Generic;

namespace Moebius.Components
{
    #region ISubsystem

    /// <summary>
    /// A subsystem that can manage components
    /// </summary>
    public interface ISubsystem
    {
        #region Register

        /// <summary>
        /// Register the subsystem with the <see cref="World"/>
        /// </summary>
        /// <param name="world">The <see cref="World"/> to register with</param>
        void Register(World world);

        #endregion
        #region Find Component For Entity

        /// <summary>
        /// Finds a specific component in this subsystem that is bound to a given <see cref="Entity"/>
        /// </summary>
        /// <param name="entity">The <see cref="Entity"/> to find a component of.</param>
        /// <returns>The component instance</returns>
        BaseComponent FindComponentForEntity(Entity entity);

        #endregion
        #region Tick

        /// <summary>
        /// Update all components in this subsystem.
        /// </summary>
        /// <param name="elapsedTime">The amount of time that has elpapsed since the subsystem was last updated.</param>
        void Tick(double elapsedTime);

        #endregion
    }

    #endregion

    #region Generic Subsystem Implementation

    /// <summary>
    /// A generic subsystem, that almost all subsystems will likely wish to derive from. This subsystem provides a basic component management system.
    /// </summary>
    /// <typeparam name="TComponentType">The type of components that this subsystem manages</typeparam>
    public abstract class GenericSubsystem<TComponentType> : ISubsystem where TComponentType : BaseComponent
    {
        #region Properties

        private readonly Dictionary<Entity, TComponentType> mComponents = new Dictionary<Entity, TComponentType>();

        /// <summary>
        /// A <see cref="Dictionary{TKey,TValue}"/> of <see cref="Entity"/>s against <typeparamref name="TComponentType"/>
        /// </summary>
        protected Dictionary<Entity, TComponentType> Components
        {
            get { return mComponents; }
        }

        /// <summary>
        /// Gets a list of all components that this subsystem manages
        /// </summary>
        public IEnumerable<TComponentType> AllComponents
        {
            get { return mComponents.Values; }
        }

        #endregion

        #region Event Handling

        void OnEntityAdded(object sender, EntityAddedEventArgs e)
        {
            if (ShouldAddEntity(e.Entity))
                mComponents.Add(e.Entity, CreateComponent(e.Entity));
        }
        void OnEntityRemoved(object sender, EntityRemovedEventArgs e)
        {
            mComponents.Remove(e.Entity);
        }

        #endregion

        #region Component Creation

        /// <summary>
        /// Helper method to decide if the subsystem manager should add a componet to the entity or not.
        /// </summary>
        /// <remarks>
        /// The default implementation checks if the <see cref="Entity"/> requires a component managed by this subsystem,
        /// and also that no component already exists for this
        /// <see cref="Entity"/>
        /// </remarks>
        /// <param name="entity">The <see cref="Entity"/> instance</param>
        /// <returns></returns>
        protected virtual bool ShouldAddEntity(Entity entity)
        {
            return !mComponents.ContainsKey(entity) && entity.RequiredComponents.Contains(typeof(TComponentType));
        }

        /// <summary>
        /// Adds a component to the subsystem.
        /// </summary>
        /// <param name="entity">The <see cref="Entity"/> that requires the component</param>
        /// <param name="component">The component to add.</param>
        protected void AddComponent(Entity entity, TComponentType component)
        {
            mComponents.Add(entity, component);
        }

        /// <summary>
        /// Create the actual component, for a specific Entity.
        /// </summary>
        /// <param name="entity">The <see cref="Entity"/> to add a component to.</param>
        /// <returns>The new <typeparamref name="TComponentType"/> instance</returns>
        protected abstract TComponentType CreateComponent(Entity entity);

        #endregion

        #region ISubsystem Implementation

        /// <summary>
        /// Register this subsystem with a specifc <see cref="World"/>
        /// </summary>
        /// <param name="world">The <see cref="World"/> to register with</param>
        public virtual void Register(World world)
        {
            world.EntityAdded += OnEntityAdded;
            world.EntityRemoved += OnEntityRemoved;
        }

        /// <summary>
        /// Finds a specific component in this subsystem that is bound to a given <see cref="Entity"/>
        /// </summary>
        /// <param name="entity">The <see cref="Entity"/> to find a component of.</param>
        /// <returns>The component instance</returns>
        public BaseComponent FindComponentForEntity(Entity entity)
        {
            if (mComponents.ContainsKey(entity))
                return mComponents[entity];
            else
                return null;
        }

        /// <summary>
        /// Update all components in this subsystem.
        /// </summary>
        /// <param name="elapsedTime">The amount of time that has elpapsed since the subsystem was last updated.</param>
        public virtual void Tick(double elapsedTime) { }

        #endregion
    }

    #endregion
}